package com.abel.bigwater.data.sanchuan

import com.abel.bigwater.data.BcdString
import com.abel.bigwater.data.Bit32Array
import com.abel.bigwater.model.JsonDateDeserializer
import com.abel.bigwater.model.JsonDateSerializer
import com.abel.bigwater.model.JsonHelper
import com.alibaba.fastjson.annotation.JSONField
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import io.netty.buffer.ByteBuf
import org.joda.time.DateTime
import org.slf4j.LoggerFactory
import java.util.*
import kotlin.collections.ArrayList
import kotlin.experimental.and
import kotlin.math.pow

class GprsOnlineData(_dataLen: Int?) : SanchuanData(_dataLen) {

    var gprsStateType: GprsStateType? = null

    /**
     * 软件版本号	00	02	3	主版本2字节， 低字节在前，0x80 0x25表示主版本为9600版本
    子版本1字节， 0x14表示子版本为20版本
    该指令读有效，写无效。
     */
    var softVersion: SoftVersionType? = null

    /**
     * 读取IMSI号	01	07	8	8字节用来表示15位IMSI号，8字节由低到高依次为：0x04,0x60,0x04,0x02,0x60,0x10,0x04,0x80则表示IMSI号为460040260100480，第1字节的高4位固定为0
    该指令读有效，写无效。
     */
    var imsi: BcdString? = null

    var totalBit: Byte? = null

    /**
     * 5 当前累计流量 格式见参数表，参数编码 1110
     * 累计流量
    格式 2 11 10 5
    16 进制无符号低字节在前，单位 0.1L。
    例： 0x00,0x01,0x00,0x00,0x00 表示当前累计流量为： 25.6L
    表示水表从激活后到当前时间累计流过的总水量。注意与参数编码(0101)格式不
    一致。
     */
    var totalReadingInt: Int? = null

    /**
     * unit: m³
     */
    var totalReading: Double? = null
        get() {
            field = totalReadingInt!!.toDouble() * Math.pow(10.0, -totalBit!!.toDouble())
            return field
        }
        set(value) {
            field = value
            totalReadingInt = (field!! * Math.pow(10.0, totalBit!!.toDouble())).toInt()
        }

    /**
     * 为BCD码，0x20 0x14 0x01 0x05 则表示2014年01月05日
     */
    @JsonIgnore
    var startDateTimeStr: BcdString? = null

    /**
     * 为BCD码，0x20 0x14 0x01 0x05 则表示2014年01月05日
     */
    @JsonSerialize(using = JsonDateSerializer::class)
    @JsonDeserialize(using = JsonDateDeserializer::class)
    @JSONField(format = JsonHelper.FULL_DATE_FORMAT)
    var startDateTime: Date? = null
        get() {
            field = if (startDateTimeStr == null) null
            else DateTime(startDateTimeStr!!.bcdArray!![0] * 100 + startDateTimeStr!!.bcdArray!![1].toInt(),
                    startDateTimeStr!!.bcdArray!![2].toInt(),
                    startDateTimeStr!!.bcdArray!![3].toInt(), 0, 0).toDate()
            return field
        }
        set(value) {
            field = value
            if (field == null)
                startDateTimeStr = null
            else {
                val dt = DateTime(field)
                startDateTimeStr = BcdString(byteArrayOf(dt.year.div(100).toByte(),
                        dt.year.rem(100).toByte(),
                        dt.monthOfYear.toByte(), dt.dayOfMonth.toByte()),
                        true)
            }
        }

    /**
     * Decimalbit 小数的位数
    Data1格式：4字节为流量值
    例：0x01 0x00 0x00 0x34 0x56 表示读数为：1339.8吨
     */
    var decimalBit: Byte? = null

    var dataIntList: Array<Int>? = null

    /**
     * compute pulse from decimal position.
     */
    var pulse: Short? = null
        get() = 10.0.pow(3.0.minus(decimalBit ?: 0)).toShort()

    /**
     * 注意：
    最近1天的日结数据是在第2天0点存储的。上报的日结数据即为该天0点的数据。如1号的日结是1号0点存储的数据。
    例：当前时间为9月30日，则回复的起始时间为8月31日，最近一天的日结数据为9月31日0点数据。
     */
    var dataList: DoubleArray? = null
        get() {
            if (dataIntList == null) field = null
            else {
                val fmt = Math.pow(10.0, -decimalBit!!.toDouble())
                val list = ArrayList<Double>()
                dataIntList.orEmpty().forEach {
                    list.add(it * fmt)
                }

                field = list.toDoubleArray()
            }

            return field
        }
        set(value) {
            field = value

            if (value == null) {
                dataIntList = null
            } else {
                val fmt = Math.pow(10.0, decimalBit!!.toDouble()).toInt()

                val list = arrayListOf<Int>()
                value.forEach {
                    list.add(it.times(fmt).toInt())
                }
                dataIntList = list.toTypedArray()
            }
        }

    init {
//        assert(dataLen == null || dataLen == FIXED_LEN)
    }

    /**
     * 计算数据长度，针对解析和打包
     */
    override fun computeDataLen(): Int {
        dataLen = FIXED_LEN
        return dataLen!!
    }

    /**
     * 从当前位置写入打包字节流
     */
    override fun fillBuffer(buf: ByteBuf) {
        buf.alloc().buffer().apply {
            writeIntLE(gprsStateType!!.back)

            writeShortLE(softVersion!!.mainVersion!!)
            writeByte(softVersion!!.childVersion!!.toInt())

            assert(imsi?.size == 8)
            writeBytes(imsi!!.hexArray)

            writeInt(totalReadingInt!!)

            writeBytes(byteArrayOf(0, 0))
            writeBytes(startDateTimeStr!!.hexArray)

            writeByte(decimalBit!!.toInt())

            assert(dataIntList?.size == 31)
            dataIntList.orEmpty().forEach {
                writeInt(it)
            }

            // transfer to buf in total
            buf.writeBytes(this)
        }
    }

    /**
     * 解析字节流，从buf当前位置开始，并且消费其中的字节。
     */
    override fun <T : SanchuanData> parseBuffer(buf: ByteBuf): T? {
        buf.readBytes(dataLen!!).apply {
            gprsStateType = GprsStateType(readIntLE())

            softVersion = SoftVersionType()
            softVersion!!.mainVersion = readShortLE().toInt().and(0xFFFF)
            softVersion!!.childVersion = readByte().toShort().and(0xFF)

            imsi = BcdString.create(this, 8)

            totalBit = readByte()
            totalReadingInt = readInt()

            // why skip 2-bytes here?
            duplicate().also {
                val tmpDate = BcdString.create(it, 4)
                try {
                    val d1 = DateTime(tmpDate.bcdArray!![0].times(100).plus(tmpDate.bcdArray!![1]).toInt(),
                            tmpDate.bcdArray!![2].toInt(),
                            tmpDate.bcdArray!![3].toInt(), 0, 0)
                    if (d1.isBefore(DateTime.now().minusYears(20))
                            || d1.isAfter(DateTime.now().plusDays(1))) {
                        throw Exception("invalid date should be skipped: " + tmpDate.bcdStringBlank())
                    }
                } catch (ex: Exception) {
                    lgr.warn("should skip 2 bytes if invalid date: {}", ex.message)
                    skipBytes(2)
                }
            }
            startDateTimeStr = BcdString.create(this, 4)

            decimalBit = readByte()

            dataIntList = Array(31) { readInt() }
        }

        return this as T
    }

    companion object {
        const val FIXED_LEN = 4 + 3 + 8 + 5 + 129
        private val lgr = LoggerFactory.getLogger(GprsOnlineData::class.java)
    }
}

class GprsStateType(v1: Int) : Bit32Array(v1) {

}
